/*
* 
* @license	Apache License 2.0
* @author	Eugene Kuznetsov
*
*/
#include "CTargetServer.hpp"
#include "CDataBuffer.hpp"
#include "CDoubleBufferQueue.hpp"
#include "CErrorHandler.hpp"
#include <string>
#include <winsock2.h>
#include <process.h>

CTargetServer::CTargetServer(std::string ip_address, UInt8 port)
	: m_eState(Link::DISCONNECTED)
	, m_bIsInited(false)
	, m_sIpAddress(ip_address)
	, m_nPort(port)
	, m_nLink(INVALID_SOCKET)
	, m_nLastError(0)
{
	DBG_L1(("[%p] %s(%s, %d)\n", this, __FUNCTION__, m_sIpAddress.c_str(), m_nPort));
}

CTargetServer::~CTargetServer()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState == Link::CONNECTED)
	{
		Link::eConnectState connect_state = Disconnect();
		if (connect_state != Link::DISCONNECTED)
		{
			DBG_L2(("[%p] connect_state(%d) != Link::DISCONNECTED\n", this, ToInt8(connect_state)));
		}
	}
}

Base::eState CTargetServer::Init()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_bIsInited)
	{
		DBG_L2(("[%p] already inited\n", this));
		return Base::STATE_OK;
	}
	/*
	* @todo
	* IP Address check
	*/
	m_nLink = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_nLink == SOCKET_ERROR)
	{
		DBG_L2(("[%p] socket call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_nLink = INVALID_SOCKET;
		return Base::STATE_NOT_OK;
	}
	DBG_L2(("[%p] initialized\n", this));
	m_bIsInited = true;
	return Base::STATE_OK;
}

Link::eConnectState CTargetServer::Connect()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (!m_bIsInited)
	{
		DBG_L2(("[%p] m_bIsInited == false\n", this));
		return Link::LINK_ERROR;
	}
	if (m_eState == Link::CONNECTED)
	{
		DBG_L2(("[%p] already connected\n", this));
		return m_eState;
	}
	sockaddr_in sai;
	sai.sin_family = AF_INET;
	sai.sin_addr.s_addr = inet_addr(m_sIpAddress.c_str());
	sai.sin_port = htons(m_nPort);
	m_eState = Link::CONNECTING;
	Int16 result = connect(m_nLink, (SOCKADDR*)&sai, sizeof(sai));
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] connect call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LINK_ERROR;
	}
	else
	{
		DBG_L2(("[%p] connected\n", this));
		m_eState = Link::CONNECTED;
	}
	return m_eState;
}

Link::eConnectState CTargetServer::Disconnect()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (!m_bIsInited)
	{
		DBG_L2(("[%p] m_bIsInited == false\n", this));
		return Link::LINK_ERROR;
	}
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] not connected -> m_eState = %d\n", this, ToUInt8(m_eState)));
		return m_eState;
	}
	m_eState = Link::DISCONNECTING;
	Int16 result = shutdown(m_nLink, SD_BOTH);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] shutdown call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LINK_ERROR;
	}
	result = closesocket(m_nLink);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] closesocket call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LINK_ERROR;
	}
	else
	{
		DBG_L2(("[%p] disconnected from %s:%d\n", this, m_sIpAddress.c_str(), m_nPort));
		m_eState = Link::DISCONNECTED;
		m_nLink = INVALID_SOCKET;
		m_bIsInited = false;
	}
	return m_eState;
}

Link::eLinkIOState CTargetServer::SendDataToServer(const CDataBuffer* buffer)
{
	DBG_L1(("[%p] %s -> send %d bytes\n", this, __FUNCTION__, buffer->size()));
	if (!m_bIsInited)
	{
		DBG_L2(("[%p] m_bIsInited == false\n", this));
		return Link::UNKNOWN_ERROR;
	}
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] link state != Link::CONNECTED\n", this));
		return Link::UNKNOWN_ERROR;
	}
	Int16 bytes = send(m_nLink, reinterpret_cast<const char*>(buffer->data()), buffer->size(), 0);
	if (bytes == -1)
	{
		DBG_L2(("[%p] send call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		return Link::WRITE_ERROR;
	}
	else
	{
		DBG_L3(("[%p] %d bytes has been sent\n", this, bytes));
	}
	return (bytes == 0) ? Link::WRITE_CLOSE : Link::WRITE_OK;
}

Link::eLinkIOState CTargetServer::ReceiveDataFromServer(CDataBuffer& buffer)
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (!m_bIsInited)
	{
		DBG_L2(("[%p] m_bIsInited == false\n", this));
		return Link::UNKNOWN_ERROR;
	}
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] link state != Link::CONNECTED\n", this));
		return Link::UNKNOWN_ERROR;
	}
	UChr temp_buffer[LINK_BUFFER_SIZE];
	memset(temp_buffer, 0, LINK_BUFFER_SIZE);
	Int16 bytes = recv(m_nLink, reinterpret_cast<char*>(temp_buffer), LINK_BUFFER_SIZE, 0);
	if (bytes == -1)
	{
		DBG_L2(("[%p] recv call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		return Link::READ_ERROR;
	}
	else
	{
		DBG_L3(("[%p] %d bytes has been received\n", this, bytes));
		if (bytes > 0)
		{
			buffer.append(temp_buffer, ToUInt32(bytes));
		}
	}
	return (bytes == 0) ? Link::READ_CLOSE : Link::READ_OK;
}

bool CTargetServer::IsReadyToReceive(Base::eState& state)
{
	DBG_L4(("[%p] %s\n", this, __FUNCTION__));
	if (!m_bIsInited)
	{
		DBG_L4(("[%p] m_bIsInited == false\n", this));
		return false;
	}
	timeval timeout = {0, 25};
	fd_set fd_read;
	FD_ZERO(&fd_read);
	FD_SET(m_nLink, &fd_read);
	Int16 result = select(0, &fd_read, NULL, NULL, &timeout);
	switch (result)
	{
		case SOCKET_ERROR:
			DBG_L2(("[%p] select call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
			state = Base::STATE_NOT_OK;
			return false;
		case 0:
			DBG_L4(("[%p] not ready to read\n", this));
			state = Base::STATE_OK;
			return false;
		case 1:
			DBG_L4(("[%p] ready to read\n", this));
			state = Base::STATE_OK;
			return true;
		default:
			state = Base::STATE_UNKNOWN;
			return true;
	}
}

Link::eConnectState CTargetServer::getState() const
{
	return m_eState;
}

Int16 CTargetServer::getLastError()
{
	Int16 err = m_nLastError;
	m_nLastError = 0;
	return err;
}

Base::eState CTargetServer::RunTargetServer(std::string ip_address, UInt8 port, CDoubleBufferQueue* buffer_queue, CErrorHandler* error_handler)
{
	DBG_L1(("[S%.7d] %s\n", GetCurrentThreadId(), __FUNCTION__));
	if ((ip_address.length() == 0) || (port == 0))
	{
		DBG_L2(("[S%.7d] ip_address or port is wrong, return STATE_NOT_OK\n", GetCurrentThreadId()));
		return Base::STATE_NOT_OK;
	}
	HStarterParams* starter_params = new HStarterParams();
	starter_params->m_sIpAddress = ip_address;
	starter_params->m_nPort = port;
	starter_params->m_pReceiveSendQueue = buffer_queue;
	starter_params->m_pErrorHandler = error_handler;
	DBG_L2(("[S%.7d] buffer_queue(%p), error_handler(%p)\n", GetCurrentThreadId(), buffer_queue, error_handler));
	if ((buffer_queue != 0) && (error_handler != 0))
	{
		DBG_L2(("[S%.7d] start Target Server thread\n", GetCurrentThreadId()));
		_beginthread(TargetServerProcess, 0, static_cast<void*>(starter_params));
		return Base::STATE_OK;
	}
	else
	{
		delete starter_params;
		DBG_L2(("[S%.7d] buffer_queue or error_handler is wrong, return STATE_NOT_OK\n", GetCurrentThreadId()));
		return Base::STATE_NOT_OK;
	}
}

void CTargetServer::TargetServerProcess(void * starter_params)
{
	DBG_L1(("[T%.7d] %s\n", GetCurrentThreadId(), __FUNCTION__));
	HStarterParams* params = static_cast<HStarterParams*>(starter_params);
	CDoubleBufferQueue & rReceiveSendQueue = *params->m_pReceiveSendQueue;
	CErrorHandler & rErrorHandler = *params->m_pErrorHandler;
	CTargetServer server(params->m_sIpAddress, params->m_nPort);
	delete params;
	Base::eState state = server.Init();
	if (state != Base::STATE_OK)
	{
		DBG_L2(("[T%.7d] state != Base::STATE_OK\n", GetCurrentThreadId()));
		//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
		return;
	}
	Link::eConnectState connect_state = server.Connect();
	if (connect_state != Link::CONNECTED)
	{
		DBG_L2(("[T%.7d] connect_state != Link::CONNECTED\n", GetCurrentThreadId()));
		//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
		return;
	}
	
	/*
	* Start of main loop of thread
	*/
	CDataBuffer buffer;
	Link::eLinkIOState io_state = Link::UNKNOWN_ERROR;
	bool ready_to_receive = false;
	while (rReceiveSendQueue.getState() == Base::STATE_OK)
	{
		if (rReceiveSendQueue.getSendQueueSize() > 0)
		{
			buffer.clear();
			// device have something to send to target server
			rReceiveSendQueue.PopFromSendQueue(buffer);
			io_state = server.SendDataToServer(&buffer);
			switch (io_state)
			{
			case Link::UNKNOWN_ERROR :
			case Link::WRITE_ERROR :
			case Link::WRITE_CLOSE :
				rReceiveSendQueue.PushToSendQueue(&buffer);
				rReceiveSendQueue.setState(Base::STATE_NOT_OK);
				//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
				break;
			case Link::WRITE_OK :
			default :
				break;
			}
		}
		ready_to_receive = server.IsReadyToReceive(state);
		if (state != Base::STATE_OK)
		{
			//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
		}
		else
		{
			buffer.clear();
			// server wants to send something to device
			while (ready_to_receive)
			{
				ready_to_receive = server.IsReadyToReceive(state);
				if ((ready_to_receive == true) && (state == Base::STATE_OK))
				{
					io_state = server.ReceiveDataFromServer(buffer);
					if (io_state != Link::READ_OK)
					{
						if (buffer.size() > 0)
						{
							rReceiveSendQueue.PushToReceiveQueue(&buffer);
						}
						rReceiveSendQueue.setState(Base::STATE_NOT_OK);
						//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
						break;
					}
				}
				else if (buffer.size() > 0)
				{
					rReceiveSendQueue.PushToReceiveQueue(&buffer);
					break;
				}
				if (state != Base::STATE_OK)
				{
					rReceiveSendQueue.setState(Base::STATE_NOT_OK);
					//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
					break;
				}
			}
		}
		// CPU relax moments :)
		Sleep(10);
	}
	/*	
	* End of main loop of thread
	*/
	DBG_L1(("[T%.7d] %s -> shutting down\n", GetCurrentThreadId(), __FUNCTION__));

	connect_state = server.Disconnect();
	if (connect_state != Link::DISCONNECTED)
	{
		DBG_L2(("[T%.7d] connect_state(%d) != Link::DISCONNECTED\n", GetCurrentThreadId(), ToInt8(connect_state)));
		//rErrorHandler.submit(GetCurrentThreadId(), __LINE__, ToInt8(server.getState()), server.getLastError());
		return;
	}
}
